package com.ssm.web.config.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.sql.DataSource;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * 安全配置
 */
@Slf4j
// @Order(1) // https://www.jianshu.com/p/fe1194ca8ecd
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;

    private final PasswordEncoder passwordEncoder;

    private final DataSource dataSource;

    private static final String[] PERMIT_URL = new String[]{
            // 不需要放开 "/oauth/**", "/login"
            "/actuator/**", "/druid/**", "/error"
    };

    public WebSecurityConfig(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService, DataSource dataSource) {
        this.userDetailsService = userDetailsService;
        this.passwordEncoder = passwordEncoder;
        this.dataSource = dataSource;
    }

    /**
     * 安全拦截机制
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /** @see CsrfFilter 跨站点请求伪造 */
        http.csrf();
        /** @see CorsConfig#corsConfiguration() 允许跨域请求，默认情况下使用名为 corsConfigurationSource 的 Bean */
        http.cors(withDefaults());
        // 退出，在开启 CSRF 后仍然使用 GET 方式提交退出请求，https://blog.csdn.net/mrleeyongsheng/article/details/78886184
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/").permitAll()
                // .logoutSuccessHandler((request, response, authentication) -> { // 退出成功返回 JSON，和 url 跳转二选一
                //     // Oauth2 退出
                //     String authorization = request.getHeader("Authorization");
                //     if (authorization != null && authorization.contains("Bearer")) {
                //         String tokenValue = authorization.replace("Bearer", "").trim();
                //         OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
                //         tokenStore.removeAccessToken(accessToken);
                //         //OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(tokenValue);
                //         OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
                //         tokenStore.removeRefreshToken(refreshToken);
                //     }
                //     Map<String, Object> map = new HashMap<>();
                //     map.put("success", "ok");
                //     response.setContentType("application/json;charset=utf-8");
                //     PrintWriter out = response.getWriter();
                //     out.write(JsonUtil.obj2String(map));
                //     out.flush();
                //     out.close();
                // })
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout", HttpMethod.GET.name()));
        // 403 无权限页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        // 登录，开启 CSRF 后需要附带发送 CSRF token
        http.formLogin().loginPage("/loginPage").loginProcessingUrl("/loginProcess")
                .defaultSuccessUrl("/").permitAll();
        // 自动登录，记住我
        http.rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60).userDetailsService(userDetailsService);
        // Basic 认证
        http.httpBasic().disable();
        // url 拦截
        http.antMatcher("/**")
                .authorizeRequests()
                /*不需要通过登录验证就可以被访问的资源路径*/
                .antMatchers(PERMIT_URL).permitAll()
                // .antMatchers("/xxx").hasAuthority("admin")
                // .antMatchers("/xxx").hasAnyAuthority("admin", "xxx")
                // .antMatchers("/xxx").hasRole("ROLE_sale")
                // .antMatchers("/xxx").access("hasRole('ROLE_sale')")
                // .antMatchers("/xxx").access("@class.method(request,authentication)") // 自定义访问控制逻辑
                // .antMatchers("/xxx").hasAnyRole("ROLE_sale", "ROLE_xxx")
                .regexMatchers(HttpMethod.GET, ".+[.]png").permitAll()
                // server.servlet.context-path: ssm
                .antMatchers("/ssm/hw").hasIpAddress("127.0.0.1")
                // https://docs.spring.io/spring-security/site/docs/5.4.5/reference/html5/#el-common-built-in
                .mvcMatchers("/hw").servletPath("/ssm").access("permitAll")
                /*其它路径都需要登陆认证*/
                .anyRequest().authenticated()
        ;
    }

    /**
     * 注入自定义的 userDetailsService 实现，获取用户信息，设置密码加密方式
     */
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                // .inMemoryAuthentication().passwordEncoder(passwordEncoder)
                // .withUser("root").password("root").roles("admin");
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder)
        ;
    }

    /**
     * 必须注入到 IOC 中
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl persistentTokenRepository = new JdbcTokenRepositoryImpl();
        persistentTokenRepository.setDataSource(dataSource);
        persistentTokenRepository.setCreateTableOnStartup(true); // 自动建表
        return persistentTokenRepository;
    }
}
